﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace FirstFloor.Documents.Pdf.Functions
{
    /// <summary>
    /// Represents a PDF function.
    /// </summary>
    internal abstract class Function
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="Function"/> class.
        /// </summary>
        /// <param name="dictionary">The function dictionary.</param>
        public Function(DictionaryObject dictionary)
        {
            this.Domain = dictionary.Get<ArrayObject>("Domain").ToArray<double>();
            this.M = this.Domain.Length / 2;

            ArrayObject range;
            if (dictionary.TryGet<ArrayObject>("Range", out range)) {
                this.Range = range.ToArray<double>();
                this.N = this.Range.Length / 2;
            }
        }

        /// <summary>
        /// Gets the domain.
        /// </summary>
        /// <value>The domain.</value>
        public double[] Domain { get; private set; }
        /// <summary>
        /// Gets the range.
        /// </summary>
        /// <value>The range.</value>
        public double[] Range { get; private set; }
        /// <summary>
        /// Gets the number of input values.
        /// </summary>
        /// <value>The number of input values.</value>
        public int M { get; private set; }
        /// <summary>
        /// Gets the number of output values.
        /// </summary>
        /// <value>The number of output values.</value>
        public int N { get; private set; }

        /// <summary>
        /// Executes this function instance.
        /// </summary>
        /// <param name="args">The args.</param>
        /// <returns></returns>
        public double[] Execute(params double[] args)
        {
            if (args.Length != this.M) {
                throw new InvalidOperationException(string.Format(Resources.InvalidFunctionParameters, this.M, args.Length));
            }

            // clip values to fit in domain
            for (int i = 0; i < args.Length; i++) {
                args[i] = Math.Max(this.Domain[2 * i], Math.Min(this.Domain[2 * i + 1], args[i]));
            }

            // execute function
            var result = OnExecute(args);

            if (this.Range != null) {
                if (result.Length != this.N) {
                    throw new InvalidOperationException(string.Format(Resources.InvalidFunctionResultValues, this.N, result.Length));
                }

                // clip values to fit in range (optional)
                for (int i = 0; i < result.Length; i++) {
                    result[i] = Math.Max(this.Range[2 * i], Math.Min(this.Range[2 * i + 1], result[i]));
                }
            }

            return result;
        }

        /// <summary>
        /// Provides the actual implementation of the function.
        /// </summary>
        /// <param name="args">The args.</param>
        /// <returns></returns>
        protected abstract double[] OnExecute(params double[] args);

        /// <summary>
        /// Creates the function.
        /// </summary>
        /// <param name="parser">The parser.</param>
        /// <param name="dictionary">The dictionary.</param>
        /// <returns></returns>
        public static Function CreateFunction(PdfParser parser, DictionaryObject dictionary)
        {
            var functionType = (FunctionType)dictionary.Get<int>("FunctionType");

            if (functionType == FunctionType.Sampled) {
                using (var stream = parser.ParseStreamObject(dictionary)) {
                    return new SampledFunction(stream);
                }
            }
            else if (functionType == FunctionType.ExponentialInterpolation) {
                return new ExponentialInterpolationFunction(dictionary);
            }
            else if (functionType == FunctionType.Stitching) {
                return new StitchingFunction(dictionary);
            }
            else if (functionType == FunctionType.PostScriptCalculator) {
                using (var stream = parser.ParseStreamObject(dictionary)) {
                    return new PostScriptCalculatorFunction(stream);
                }
            }
            throw new NotSupportedException(string.Format("Invalid function type {0}", functionType));
        }
    }
}
